package com.chuangjiangx.member.stored.service;

import com.alibaba.fastjson.JSON;
import com.chuangjiangx.commons.CurrentThreadContext;
import com.chuangjiangx.commons.JacksonUtils;
import com.chuangjiangx.commons.exception.BaseException;
import com.chuangjiangx.commons.response.CamelResponse;
import com.chuangjiangx.domain.shared.model.PayEntry;
import com.chuangjiangx.domain.shared.model.PayTerminal;
import com.chuangjiangx.domain.shared.model.PayType;
import com.chuangjiangx.member.stored.ddd.application.MbrPayApplication;
import com.chuangjiangx.member.stored.ddd.application.command.MbrUniformOrdersCommand;
import com.chuangjiangx.member.stored.ddd.application.command.MbrWebPagePayCommand;
import com.chuangjiangx.member.stored.ddd.application.dto.MbrPayOrderResult;
import com.chuangjiangx.member.stored.ddd.application.dto.MbrUniformOrdersResult;
import com.chuangjiangx.member.coupon.ddd.domain.model.MbrHasCoupon;
import com.chuangjiangx.member.stored.ddd.domain.model.MbrOrder;
import com.chuangjiangx.member.stored.ddd.domain.model.MbrOrderRepository;
import com.chuangjiangx.member.stored.ddd.domain.MbrPaymentConstant;
import com.chuangjiangx.member.basic.web.exceptions.MbrRechargeException;
import com.chuangjiangx.member.stored.web.request.CtobPayRequest;
import com.chuangjiangx.member.stored.web.request.RechargeRequest;
import com.chuangjiangx.member.stored.web.response.CtobPayResponse;
import com.chuangjiangx.member.stored.web.response.PayResultResponse;
import com.chuangjiangx.member.stored.service.command.H5PayCommand;
import com.chuangjiangx.member.stored.service.command.PayMbrRechargeOrderCommand;
import com.chuangjiangx.member.stored.service.model.*;
import com.chuangjiangx.member.basic.ddd.query.MemberQuery;
import com.chuangjiangx.member.basic.ddd.query.MerchantUserInfoQuery;
import com.chuangjiangx.member.basic.ddd.query.dto.MemberDTO;
import com.chuangjiangx.member.basic.ddd.query.dto.UserInfoDTO;
import com.chuangjiangx.member.basic.ddd.domain.model.MbrUserContext;
import com.chuangjiangx.member.stored.ddd.domain.exception.MemberPaymentException;
import com.chuangjiangx.member.stored.ddd.domain.model.MbrOrderId;
import com.chuangjiangx.member.stored.ddd.domain.model.MbrOrderStatus;
import com.chuangjiangx.member.stored.ddd.domain.model.MbrUniformOrderType;
import com.chuangjiangx.member.common.utils.RequestUtils;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
 * @author cj-ch
 * @date 2018/7/18 下午3:11
 */
@Service
@Slf4j
public class H5PayService {
    @Value("${member.h5.domain}")
    private String h5Domain;

    @Autowired
    private MbrPayApplication mbrPayApplication;
    @Autowired
    private MemberQuery memberQuery;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private MerchantUserInfoQuery merchantUserInfoQuery;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private ClientApiService clientApiService;
    @Autowired
    private MbrOrderRepository mbrOrderRepository;




    @Transactional(rollbackFor = Exception.class)
    public CtobPayResponse pay(H5PayCommand command){
        final boolean useCoupon = command.isUseCoupon();
        final PayEntry payEntry = command.getPayEntry();
        final MbrHasCoupon mbrHasCoupon = command.getMbrHasCoupon();
        final MemberDTO memberDTO = command.getMemberDTO();
        final CtobPayRequest request = command.getRequest();
        CtobPayResponse response = new CtobPayResponse();
        response.setPayEntry(payEntry.value);
        if(useCoupon){
            if(Objects.equals(PayEntry.MSCARDPAY,payEntry)){
                //用户选择会员卡支付
                memberPay(request,memberDTO.getId(),mbrHasCoupon.getVerifyCode(),response);
            }else{
                /*
                使用了卡券但是用户选择了非会员卡支付方式
                    1.会员下单
                    2.移动支付下单
                    3.调起移动支付
                 */
                otherPayUseCoupon(command,response);

            }
        }else{
            //未使用卡券
            if(Objects.equals(PayEntry.MSCARDPAY,payEntry)){
                //用户选择会员卡支付
                memberPay(request,memberDTO.getId(),null,response);
            }else{
                //调起移动支付
                otherPayNoCoupon(command,response);
            }
        }

        return response;
    }


    /**
     * 用户使用卡券并且选择使用非会员卡支付
     * @param command
     */
    private void otherPayUseCoupon(H5PayCommand command,CtobPayResponse response) {
        final PayEntry payEntry = command.getPayEntry();
        final MbrHasCoupon mbrHasCoupon = command.getMbrHasCoupon();
        final CtobPayRequest request = command.getRequest();
        MbrUserContext user = (MbrUserContext) CurrentThreadContext.getCurrentUser();
        Long merchantUserId = null;
        //商户用户信息
        UserInfoDTO userInfoDTO = null;
        if(null != request.getMerchantUserId()){
            userInfoDTO = merchantUserInfoQuery.queryUserInfo(request.getMerchantUserId());
            merchantUserId = userInfoDTO.getMerchantUserId();
        }else{
            merchantUserId = merchantUserInfoQuery.getDefaultMerchantUserId(request.getMerchantId());
        }
        //会员业务下单
        MbrUniformOrdersCommand c = new MbrUniformOrdersCommand();
        c.setType(MbrUniformOrderType.COUPON);
        c.setCouponCode(mbrHasCoupon.getVerifyCode());
        c.setAmount(request.getAmount());
        c.setMerchantId(request.getMerchantId());
        c.setStoreId(request.getStoreId());
        if(null != userInfoDTO){
            c.setMerchantUserId(userInfoDTO.getMerchantUserId());
            c.setStoreUserId(userInfoDTO.getStoreUserId());
        }
        //会员业务下单
        MbrUniformOrdersResult mbrOrderResult = mbrPayApplication.uniformOrders(c);

        //调起移动支付
        switch (payEntry){
            case WXPAY:
                //微信
                CtoBThirdPartyPayRequest orderRequest = CtoBThirdPartyPayRequest.builder()
                        .outTradeNo(mbrOrderResult.getOrderNumber())
                        .merchantId(request.getMerchantId())
                        .merchantUserId(merchantUserId)
                        .payTerminal(PayTerminal.MBR_H5_TERMINAL.code)
                        .amount(mbrOrderResult.getNeedPayAmount())
                        .goodName("H5下单")
                        .attach("")
                        .callBackUrl(h5Domain + "/h5/pay/call-back")
                        .note("")
                        .payEntry(payEntry.value)
                        .wx(new WxMicroPayRequest(
                                PayTerminal.MBR_H5_TERMINAL.code,
                                mbrOrderResult.getNeedPayAmount(),
                                request.getOpenid(),
                                payEntry.value,
                                RequestUtils.getRequestIp(command.getHttpServletRequest()),
                                request.getWebsocketId(),
                                "",
                                org.apache.commons.lang.StringUtils.isBlank(user.getSubOpenId())? null:user.getSubOpenId(),
                                org.apache.commons.lang.StringUtils.isBlank(user.getSubAppId())? null:user.getSubAppId()
                        ))
                        .build();
                //请求创建订单并且支付
                WxMicroPayResult wxMicroPayResult = requestCreateOrder(
                        clientApiService.getClientApiThirdPartyCTBUrl(),
                        orderRequest,
                        CtoBPayResponse.class,
                        camelResponse -> ((CtoBPayResponse) camelResponse.getData()).getWxResult());
                response.setWxMicroPayResult(wxMicroPayResult);
                break;
//            case ALIPAY:
//                AliMicroPayCommand aliMicroPayCommand = new AliMicroPayCommand(
//                        PayTerminal.MBR_H5_TERMINAL,
//                        new Money(mbrOrderResult.getNeedPayAmount().doubleValue()),
//                        new AliUserId(request.getOpenid()),
//                        PayEntry.ALIPAY,
//                        StringUtils.isBlank(request.getWebsocketId()) ? null : new WebSocketId(request.getWebsocketId()),
//                        ""
//                );
//                AliMicroPayResult aliMicroPayResult = payApplication.aliMicroPay(new PayOrderId(precreate.getOrderId()), aliMicroPayCommand);
//                response.setAliMicroPayResult(aliMicroPayResult);
            default:
                throw new MemberPaymentException("","暂时不支持支付方式:"+payEntry);
        }
    }

    /**
     * 用户未使用卡券并且选择使用非会员卡支付
     * @param command
     */
    private void otherPayNoCoupon(H5PayCommand command,CtobPayResponse response) {
        final PayEntry payEntry = command.getPayEntry();
        final CtobPayRequest request = command.getRequest();
        final String qrcodeId = request.getQrcodeId();
        Long merchantUserId = null;
        //商户用户信息
        UserInfoDTO userInfoDTO = null;
        if(null != request.getMerchantUserId()){
            userInfoDTO = merchantUserInfoQuery.queryUserInfo(request.getMerchantUserId());
            merchantUserId = userInfoDTO.getMerchantUserId();
        }else{
            merchantUserId = merchantUserInfoQuery.getDefaultMerchantUserId(request.getMerchantId());
        }
        switch (payEntry){
            case WXPAY:
                //微信
                CtoBPayRequest req = new CtoBPayRequest();
                if(StringUtils.isNotBlank(qrcodeId)){
                    req.setQrcodeId(Long.valueOf(qrcodeId));
                }else{
                    req.setMerchantUserId(merchantUserId);
                }
                req.setPayEntry(payEntry.value);
                req.setWx(new WxMicroPayRequest(
                        PayTerminal.MBR_H5_TERMINAL.code,
                        request.getAmount(),
                        request.getOpenid(),
                        payEntry.value,
                        RequestUtils.getRequestIp(command.getHttpServletRequest()),
                        request.getWebsocketId(),
                        "",
                        "",
                        ""
                ));
                //请求微信支付
                WxMicroPayResult wxMicroPayResult = requestCreateOrder(
                        clientApiService.getClientApiCTBUrl(),
                        req,
                        CtoBPayResponse.class,
                        camelResponse -> ((CtoBPayResponse) camelResponse.getData()).getWxResult());
                response.setWxMicroPayResult(wxMicroPayResult);
                break;
//            case ALIPAY:
//                AliMicroPayCommand aliMicroPayCommand = new AliMicroPayCommand(
//                        PayTerminal.MBR_H5_TERMINAL,
//                        new Money(request.getAmount().doubleValue()),
//                        new AliUserId(request.getOpenid()),
//                        PayEntry.ALIPAY,
//                        StringUtils.isBlank(request.getWebsocketId()) ? null : new WebSocketId(request.getWebsocketId()),
//                        ""
//                );
//                AliMicroPayResult aliMicroPayResult = payApplication.aliMicroPay(new MerchantUserId(merchantUserId), aliMicroPayCommand);
//                response.setAliMicroPayResult(aliMicroPayResult);
            default:
                throw new MemberPaymentException("","暂时不支持支付方式:"+payEntry);
        }
    }

    /**
     * 请求下单
     * @param request 请求参数对象
     * @param responseClass  返回对象类型
     * @param function 提取结果
     * @param <R> 返回对象泛型
     * @param <P> 参数对象泛型
     * @return
     */
    private <R,P> R requestCreateOrder(String url,P request, Class responseClass, Function<CamelResponse,R> function) {
        JavaType javaType = TypeFactory.defaultInstance().constructParametricType(CamelResponse.class, responseClass);
        RequestUtils.ReqResponse<CamelResponse<CtoBPayResponse>> reqResponse = RequestUtils.postForObject(url, request, javaType);
        //请求是否成功
        if(reqResponse.isSuccess()){
            CamelResponse data = reqResponse.getData();
            //业务处理是否成功
            if(data.isSuccess()){
                //提取结果
                return function.apply(data);
            }else{
                throw new MemberPaymentException("","下单失败,请重试");
            }
        }else if(reqResponse.isSocketTimeout()){
            throw new MemberPaymentException("","请求下单超时,请重试");
        }else{
            throw new MemberPaymentException("","请求下单失败,请重试");
        }
    }

    /**
     * 储值卡充值下单
     * @param user
     * @param request
     */
    public RechargeOrder mbrRechargeUnifiedOrder(MbrUserContext user, RechargeRequest request){
        if(null == request.getRechargeRuleId() && null != request.getCustomRechargeAmount()){
            request.setRechargeRuleId(-1L);
        }
        RechargeOrder order = new RechargeOrder();
        MbrUniformOrdersCommand command = new MbrUniformOrdersCommand();
        command.setType(MbrUniformOrderType.RECHARGE);
        command.setRechargeMemberId(user.getMemberId());
        command.setRechargeRuleId(request.getRechargeRuleId());
        //自定义充值时需要设置这个金额
        command.setAmount(request.getCustomRechargeAmount());
        command.setMerchantId(user.getMerchantId());
        command.setMerchantUserId(user.getMerchantUserId());
        //会员业务下单
        MbrUniformOrdersResult result = mbrPayApplication.uniformOrders(command);

        //移动支付下单
        CtoBThirdPartyPayRequest orderRequest = CtoBThirdPartyPayRequest.builder()
                .outTradeNo(result.getOrderNumber())
                .merchantId(user.getMerchantId())
                .merchantUserId(user.getMerchantUserId())
                .payTerminal(PayTerminal.MBR_H5_TERMINAL.code)
                .amount(result.getNeedPayAmount())
                .goodName("H5充值订单")
                .attach("")
                .callBackUrl(h5Domain + "/h5/recharge/call-back?mbrOrderId="+result.getOrderId())
                .note("")
                .build();
        //请求创建订单
        CtoBThirdPartyUnifiedOrderResponse r = requestCreateOrder(
                clientApiService.getClientApiThirdPartyUnifiedOrderUrl(),
                orderRequest,
                CtoBThirdPartyUnifiedOrderResponse.class,
                s -> (CtoBThirdPartyUnifiedOrderResponse)s.getData());
        order.setExpireTime(r.getExpireTime());
        order.setOrderPayId(r.getOrderId());
        order.setMemberOrder(result);
        return order;
    }

    /**
     * 支付储值卡充值订单
     * @param command
     * @param res
     */
    public void payMbrRechargeOrder(PayMbrRechargeOrderCommand command, PayResultResponse res){
        final Long mbrOrderId = command.getMbrOrderId();
        final MbrUserContext user = command.getUser();
        final PayEntry payEntry = command.getPayEntry();
        final Long orderPayId = command.getOrderPayId();

        MbrOrder mbrOrder = mbrOrderRepository.fromId(new MbrOrderId(mbrOrderId));
        if(null == mbrOrder){
            throw new BaseException("","订单不存在");
        }
        if(!Objects.equals(MbrOrderStatus.NOT_PAID,mbrOrder.getStatus())){
            throw new BaseException("","订单状态未处于未支付状态");
        }
        if(StringUtils.isBlank(user.getOpenId()) && StringUtils.isBlank(user.getSubOpenId())){
            throw new BaseException("","用户openid不存在");
        }
        if(Objects.equals(PayEntry.WXPAY,payEntry)){
            //微信
            CtoBPayRequest req = new CtoBPayRequest();
            req.setOrderId(orderPayId);
            req.setPayEntry(payEntry.value);
            req.setWx(new WxMicroPayRequest(
                    PayTerminal.MBR_H5_TERMINAL.code,
                    mbrOrder.getOrderAmount(),
                    StringUtils.isBlank(user.getOpenId()) ? null : user.getOpenId(),
                    payEntry.value,
                    command.getIp(),
                    "",
                    "",
                    org.apache.commons.lang.StringUtils.isBlank(user.getSubOpenId())? null:user.getSubOpenId(),
                    org.apache.commons.lang.StringUtils.isBlank(user.getSubAppId())? null:user.getSubAppId()
            ));
            log.info("请求参数req为 ======== {}" , JSON.toJSONString(req));
            //请求微信支付
            WxMicroPayResult wxMicroPayResult = requestCreateOrder(
                    clientApiService.getClientApiCTBUrl(),
                    req,
                    CtoBPayResponse.class,
                    camelResponse -> ((CtoBPayResponse) camelResponse.getData()).getWxResult());
            log.info("请求微信支付 wxMicroPayResult 参数 ========= {}" , JSON.toJSONString(wxMicroPayResult));
            res.setWxMicroPayResult(wxMicroPayResult);
        }else{
            throw new MbrRechargeException("","暂不支持:"+payEntry);
        }
    }

    private void memberPay(CtobPayRequest request, Long memberId, String couponCode,CtobPayResponse response) {
        MbrWebPagePayCommand c = new MbrWebPagePayCommand();
        c.setAmount(request.getAmount());
        c.setCouponCode(couponCode);
        c.setMemberId(memberId);
        c.setMerchantId(request.getMerchantId());
        c.setPayTerminal(PayTerminal.MBR_H5_TERMINAL.code);
        c.setStoreId(request.getStoreId());
        if(null != request.getMerchantUserId()){
            c.setMerchantUserId(request.getMerchantUserId());
            UserInfoDTO userInfoDTO = merchantUserInfoQuery.queryUserInfo(request.getMerchantUserId());
            c.setStoreUserId(userInfoDTO.getStoreUserId());
        }

        MbrPayOrderResult mbrOrderResult = mbrPayApplication.webPagePay(c);
        response.setMbrPayOrderResult(mbrOrderResult);

        if(StringUtils.isNotBlank(request.getWebsocketId()) && MbrPaymentConstant.SUCCESS.equals(mbrOrderResult.getResultCode())){
            //TODO websocket推送
            try {
                OrderPayEvent orderPayEvent = OrderPayEvent.builder()
                        .id(mbrOrderResult.getOrderId())
                        .orderNumber(mbrOrderResult.getOrderNumber())
                        .score(mbrOrderResult.getScore())
                        .isMember(true)
                        .memberMobile(mbrOrderResult.getMobile())
                        .availableAmount(mbrOrderResult.getAvailableBalance())
                        .availableScore(mbrOrderResult.getAvailableScore())

                        .merchantId(request.getMerchantId())
                        .storeId(request.getStoreId())
                        .storeUserId(null)
                        .merchantUserId(request.getMerchantUserId())
                        .orderAmount(mbrOrderResult.getTotalFee())
                        .discountAmount(mbrOrderResult.getDiscountFee())
                        .realPayAmount(mbrOrderResult.getRealPayFee())
                        .paidInAmount(mbrOrderResult.getRealPayFee())
                        .payTime(mbrOrderResult.getPayTime())
                        .channel((byte) PayTerminal.MBR_H5_TERMINAL.code)   //设备（1：pc；2：安卓；3：ios；4：收银API；5：二维码；6：拉卡拉；原前端渠道）
                        .payWay((byte) PayType.MICRO_PAY.value) //支付方式 （1：扫码支付；2：公众号支付；3：拉卡拉支付；原支付入口）
                        .type((byte) PayEntry.MSCARDPAY.value)  //支付入口 (支付入口，0：微信，1：支付宝；2：银行卡；原支付方式)
                        .payChannel((byte) 15)  //支付渠道 （1：微信服务商；2：微信商户；3：微信小微；4：支付宝服务商；5：支付宝商户；6：拉卡拉）
                        .status(mbrOrderResult.getPayStatus().byteValue())
                        .build();

                Map<String, Object> map = new HashMap<>(8);
                Map<String, Object> map2 = new HashMap<>(8);
                map2.put("data", orderPayEvent);
                map2.put("success", "true");
                map.put("data", map2);
                map.put("id", request.getWebsocketId());
                redisTemplate.convertAndSend("java", JacksonUtils.toJson(objectMapper,map));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
